﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace GameEngine
{
    /// <summary>
    /// Class representing gameboard of any size
    /// </summary>
    public class GameBoard
    {
        /// <summary>
        /// Map of figures itself
        /// </summary>
        private enumFigureType[,] m_Board;
        
        /// <summary>
        /// Size of gameboard, valid values are 8, 10, 12
        /// </summary>
        private int m_Size;

        /// <summary>
        /// Depicts the player on move
        /// </summary>
        private enumFigureColor m_onMove;

        /// <summary>
        /// if true, the gameboard has changed white and black fields
        /// it is necessary for some types of checkers like Italian
        /// which are played with "flipped" checkers board
        /// </summary>
        private bool _bFlipped;

        /// <summary>
        /// Indicates whether given checkers are played on white fields
        /// </summary>
        private bool _bFiguresOnWhiteFields;

        public GameBoard(int size, bool bFlipped = false, bool bFiguresOnWhiteFields = false)
        {
            m_Size = size;
            m_Board = new enumFigureType[m_Size, m_Size];
            m_onMove = enumFigureColor.White;
            _bFlipped = bFlipped;
            _bFiguresOnWhiteFields = bFiguresOnWhiteFields;

            CleanBoard();
        }

        /// <summary>
        /// Returns size of gameboard
        /// </summary>
        public int Size
        {
            get { return m_Size; }
        }

        /// <summary>
        /// Returns true, if in left lower corner is white field, that is
        /// the checkersboard has exchanged white and black fields 
        /// </summary>
        public bool IsFlipped
        {
            get { return _bFlipped; }
        }

        /// <summary>
        /// Returns player on move
        /// </summary>
        public enumFigureColor OnMove
        {
            get { return m_onMove; }
        }

        /// <summary>
        /// Changes player on move
        /// </summary>
        public void ChangeOnMove()
        {
            m_onMove = m_onMove.Opposite();
        }

        /// <summary>
        /// Directly sets player on move
        /// </summary>
        public void SetOnMove(enumFigureColor onMove)
        {
            m_onMove = onMove;
        }

        /// <summary>
        /// Returns figure color
        /// </summary>
        public enumFigureColor GetFieldColor(int row, int col)
        {
            enumFigureColor fieldColor = (row + col) % 2 == 0 ? enumFigureColor.Black : enumFigureColor.White;

            // hack, when playing the selected types of checkers on white fields
            // for the game engine rules it looks like playing on black fields
            // due to the saving time for whole game logic refactoring
            return (_bFlipped || _bFiguresOnWhiteFields) ? fieldColor.Opposite() : fieldColor;
        }

        /// <summary>
        /// Checks if field is outside  board
        /// </summary>
        public bool IsOutsideBoard(int row, int col)
        {
            if (row < 0 || col < 0 || row >= m_Size || col >= m_Size)
                return true;
            else return false;
        }

        /// <summary>
        /// Overload for position, checks if field coordinates are outside board
        /// </summary>
        public bool IsOutsideBoard(Position pos)
        {
            return IsOutsideBoard(pos.Row, pos.Col);
        }


        /// <summary>
        /// Makes the board empty
        /// </summary>
        public void CleanBoard()
        {
            for (int i = 0; i < m_Size; i++)
                for (int j = 0; j < m_Size; j++)
                    m_Board[i, j] = enumFigureType.Nothing;
        }

        /// <summary>
        /// Sets startup position for given number of rows of pieces
        /// </summary>
        /// <param name="rows">Number of piece rows</param>
        public void SetupStartPosition(int rows)
        {
            CleanBoard();

            m_onMove = enumFigureColor.White;

            for (int row = 0; row < rows; row++)
                for (int col = 0; col < m_Size; col++)
                    if (GetFieldColor(row, col) == enumFigureColor.Black)
                        m_Board[row, col] = enumFigureType.WhiteMan;

            for(int row = m_Size - 1; row >= m_Size - rows; row--)
                for (int col = 0; col < m_Size; col++)
                    if (GetFieldColor(row, col) == enumFigureColor.Black)
                        m_Board[row, col] = enumFigureType.BlackMan;
        }

        /// <summary>
        /// Checks, whether position on board in startup - just for Czech checkers
        /// </summary>
        /// <returns></returns>
        public bool IsStartPosition()
        {
            if (OnMove != enumFigureColor.White)
                return false;

            const int numRows = 3;
            // check white figures
            for (int row = 0; row < numRows; row++)
                for (int col = 0; col < Size; col++)
                    if (GetFieldColor(row, col) == enumFigureColor.Black)
                        if (GetField(row, col) != enumFigureType.WhiteMan)
                            return false;

            // check black figures
            for (int row = Size - 1; row > Size - 1 - numRows; row--)
                for (int col = 0; col < Size; col++)
                    if (GetFieldColor(row, col) == enumFigureColor.Black)
                        if (GetField(row, col) != enumFigureType.BlackMan)
                            return false;

            // check empty fields
            for (int row = numRows; row < Size - numRows; row++)
                for (int col = 0; col < Size; col++)
                    if (GetFieldColor(row, col) == enumFigureColor.Black)
                        if (GetField(row, col) != enumFigureType.Nothing)
                            return false;

            return true;        
        }

        /// <summary>
        /// Does move on the gameboard
        /// </summary>
        public void DoMove(Move move)
        {
            enumFigureType figure = RemoveFigure(move.startPos);
            PlaceFigure(move.endPos, figure);

            if (move.RemovingFigure == true)
            {
                move.removedFigure = RemoveFigure(move.removedFigurePlace);
            }

            if (move.convertedToKing == true)
            {
                PlaceFigure(move.endPos, GetField(move.endPos).FigureColor() == enumFigureColor.White
                    ? enumFigureType.WhiteKing : enumFigureType.BlackKing);
            }

            if (move.nextMove != null)
                DoMove(move.nextMove);           
        }

        /// <summary>
        /// Undoes move, enough to do unpacking position
        /// </summary>
        public void UndoMove(Move move)
        {
            UnpackGameBoard(move.bitOriginalPosition);           
        }

        /// <summary>
        /// Returns figure at given position
        /// </summary>
        public enumFigureType GetField(int row, int col)
        {
            return m_Board[row, col];
        }

        /// <summary>
        /// Overload, returns figure at given position
        /// </summary>
        public enumFigureType GetField(Position pos)
        {
            return GetField(pos.Row, pos.Col);
        }

        /// <summary>
        /// Puts figure at given position
        /// </summary>
        public void PlaceFigure(int row, int col, enumFigureType figure)
        {
            Debug.Assert(GetFieldColor(row, col) == enumFigureColor.Black);
            m_Board[row, col] = figure;           
        }

        /// <summary>
        /// Overload. Puts figure at given position.
        /// </summary>
        public void PlaceFigure(Position pos, enumFigureType figure)
        {
            PlaceFigure(pos.Row, pos.Col, figure);
        }

        /// <summary>
        /// Makes given field empty. 
        /// </summary>
        public enumFigureType RemoveFigure(int row, int col)
        {
            enumFigureType figure = m_Board[row, col];
            m_Board[row, col] = enumFigureType.Nothing;
            return figure;
        }

        /// <summary>
        /// Position overload. Makes given field empty.
        /// </summary>
        public enumFigureType RemoveFigure(Position pos)
        {
            return RemoveFigure(pos.Row, pos.Col);
        }

        /// <summary>
        /// Counts given figure on the board.
        /// </summary>
        /// <param name="figureType">Figure type to count</param>
        public int CountFigures(enumFigureType figureType)
        {
            int count = 0;
            for(int row = 0; row < Size; row++)
                for (int col = 0; col < Size; col++)
                {
                    if (GetField(row, col) == figureType)
                        count++;
                }

            return count;
        }

        /// <summary>
        /// Prepares bitboard mask for given figure type
        /// </summary>
        private void PackFigureType(ref ulong mask, enumFigureType figureType)
        {
            mask = 0;
            int index = 0;
            for(int row = 0; row < m_Size; row++)
                for (int col = 0; col < m_Size; col++)
                {
                    if (GetFieldColor(row, col) == enumFigureColor.Black)
                    {
                        if (GetField(row, col) == figureType)
                        {
                            mask |= (ulong)1 << index;
                        }

                        index++;
                    }
                }
        }

        /// <summary>
        /// Unpacks bitboard mask for given figure type
        /// </summary>
        private void UnpackFigureType(ulong mask, enumFigureType figureType)
        {
            int index = 0;
            for (int row = 0; row < m_Size; row++)
                for (int col = 0; col < m_Size; col++)
                {
                    if (GetFieldColor(row, col) == enumFigureColor.Black)
                    {
                        if ( (mask & ((ulong)1 << index)) != 0)
                        {
                            PlaceFigure(row, col, figureType);                           
                        }

                        index++;
                    }
                }
        }

        /// <summary>
        /// Packs gameboard to bitboard
        /// </summary>
        public BitBoard PackGameBoard()
        {
            BitBoard bitBoard = new BitBoard();

            PackFigureType(ref bitBoard.bitWM, enumFigureType.WhiteMan);
            PackFigureType(ref bitBoard.bitWQ, enumFigureType.WhiteKing);
            PackFigureType(ref bitBoard.bitBM, enumFigureType.BlackMan);
            PackFigureType(ref bitBoard.bitBQ, enumFigureType.BlackKing);

            bitBoard.onMove = m_onMove;

            return bitBoard;
        }

        /// <summary>
        /// Unpacks gameboard from bitboard
        /// </summary>
        public void UnpackGameBoard(BitBoard bitBoard)
        {
            CleanBoard();

            m_onMove = bitBoard.onMove;

            UnpackFigureType(bitBoard.bitWM, enumFigureType.WhiteMan);
            UnpackFigureType(bitBoard.bitWQ, enumFigureType.WhiteKing);
            UnpackFigureType(bitBoard.bitBM, enumFigureType.BlackMan);
            UnpackFigureType(bitBoard.bitBQ, enumFigureType.BlackKing);
        }

        /// <summary>
        /// Parses search string from GameArchiver and puts in on the board
        /// </summary>
        public void ParseSearchString(string sSearchString)
        {
            CleanBoard();

            string[] arrParts = sSearchString.Split(':');
            if (arrParts.Length == 0)
                return;

            // set on move
            SetOnMove(arrParts[0] == "W" ? enumFigureColor.White : enumFigureColor.Black);

            // place figures
            for (int partIndex = 1; partIndex < arrParts.Length; partIndex++)
            {
                //if(arrParts[partIndex].Length != 3)
                   // throw new ApplicationException("Invalid Search String");
                
                // detect figure type
                enumFigureType figureType = enumFigureType.Nothing;
                switch (arrParts[partIndex][0])
                {
                    case 'w':
                        figureType = enumFigureType.WhiteMan;
                        break;
                    case 'b':
                        figureType = enumFigureType.BlackMan;
                        break;
                    case 'W':
                        figureType = enumFigureType.WhiteKing;
                        break;
                    case 'B':
                        figureType = enumFigureType.BlackKing;
                        break;
                } // switch

                
                // parse position
                int row = 0;
                int.TryParse(arrParts[partIndex][1].ToString(), out row);
                   /// throw new ApplicationException("Invalid Search String");
                int col = 0;
                int.TryParse(arrParts[partIndex][2].ToString(), out col);
                    //throw new ApplicationException("Invalid Search String");

                if(figureType != enumFigureType.Nothing)
                    PlaceFigure(row, col, figureType);
            }
        }

        /// <summary>
        /// Creates the search string for Game Archiver from the board
        /// </summary>
        public string ToSearchString()
        {
            string sSearchString = OnMove == enumFigureColor.White ? "W" : "B";
            sSearchString += ":";

            for (int row = 0; row < Size; row++)
            {
                for (int col = 0; col < Size; col++)
                {
                    enumFigureType figureType = GetField(row, col);
                    if (figureType != enumFigureType.Nothing)
                    {
                        switch (figureType)
                        {
                            case enumFigureType.WhiteMan:
                                sSearchString += "w";
                                break;
                            case enumFigureType.BlackMan:
                                sSearchString += "b";
                                break;
                            case enumFigureType.WhiteKing:
                                sSearchString += "W";
                                break;
                            case enumFigureType.BlackKing:
                                sSearchString += "B";
                                break;
                        }
                        sSearchString += row.ToString() + col.ToString() + ":";
                    }
                }
            } // for

            // remove the last colon
            return sSearchString.Substring(0, sSearchString.Length - 1);
        }
    }
}
